﻿using System;
using System.Reflection.Emit;
using Codemonk.Common.Caching;
using System.Reflection;

namespace Codemonk.Common.Emit
{
    public sealed class DynamicAssembly : IDynamicAssembly
    {
        private DynamicAssembly() { }

        private static DynamicAssembly instance;
        public static DynamicAssembly Instance
        {
            get
            {
                if (instance == null)
                {
                    lock (sync)
                    {
                        if (instance == null) instance = new DynamicAssembly();
                    }
                }
                return instance;
            }
        }

        private bool saveAssembly = false;
        private static string assemblyName = "codemonk.dynamicassembly";
        private static string assemblyFileName = "./Codemonk.DynamicAssembly.dll";
        private static readonly object sync = new object();


        private ICacheCollection<string, ModuleBuilder> cache = new DictionaryCache<string, ModuleBuilder>();

        public ModuleBuilder GetModuleBuilder(string name = "default")
        {
            ModuleBuilder builder;
            if (cache.TryGet(name, out builder)) return builder;
            builder = CreateModule(name);
            cache.Set(name, builder);
            return builder;
        }

        private ModuleBuilder CreateModule(string name)
        {
            return saveAssembly ? GetAssembly().DefineDynamicModule("MainModule", assemblyFileName) :
                GetAssembly().DefineDynamicModule("MainModule");
        }

        private AssemblyBuilder assembly;
        private AssemblyBuilder GetAssembly()
        {
            if (assembly == null)
            {
                lock (sync)
                {
                    if (assembly == null)
                    {
                        AssemblyBuilderAccess assemblyBuilderAccess = this.saveAssembly ? AssemblyBuilderAccess.RunAndSave : AssemblyBuilderAccess.Run;
                        assembly = AppDomain.CreateDomain(assemblyName).DefineDynamicAssembly(new AssemblyName(assemblyName), assemblyBuilderAccess);
                    }
                }
            }
            return assembly;
        }

        public TypeBuilder CreateTypeBuilder(string name, string moduleName = "default")
        {
            return CreateModule(moduleName).DefineType(name, TypeAttributes.Public | TypeAttributes.Class);
        }

        ~DynamicAssembly()
        {
            if (saveAssembly)
            {
                try
                {
                    GetAssembly().Save(assemblyFileName);
                }
                catch { }
            }
        }
    }
}
